﻿using System;
using Microsoft.SPOT.Hardware;
using GTI = Gadgeteer.Interfaces;
using GTM = Gadgeteer.Modules;

namespace Gadgeteer.Modules.DFRobot
{
    /// <summary>
    /// A SD2405 Real-Time Clock module for Microsoft .NET Gadgeteer
    /// </summary>
    public class SD2405_Real_Time_Clock : GTM.Module
    {
        #region Constants

        private const ushort SD2405Address = 0x32;

        #endregion

        #region Fields

        private readonly GTI.I2CBus _i2C;
        private const int I2CClockRateKHz = 400;
        private const int I2CTimeout = 1000;
        private readonly Socket _socket;

        #endregion


        // Note: A constructor summary is auto-generated by the doc builder.
        /// <summary></summary>
        /// <param name="socketNumber">The socket that this module is plugged in to.</param>
        public SD2405_Real_Time_Clock(int socketNumber)
        {
            _socket = Socket.GetSocket(socketNumber, true, this, null);

            _socket.EnsureTypeIsSupported(new char[] { 'I' }, this);

            _i2C = new GTI.I2CBus(_socket, SD2405Address, I2CClockRateKHz, this);
        }

        /// <summary>
        /// Gets current Real Time Clock date time.
        /// </summary>
        public DateTime GetDateTime()
        {
            I2CDevice.I2CWriteTransaction write = I2CDevice.CreateWriteTransaction(new byte[] { 0x00 });

            var returnedDateTime = new byte[7];

            I2CDevice.I2CReadTransaction read = I2CDevice.CreateReadTransaction(returnedDateTime);

            var readTransaction = new I2CDevice.I2CTransaction[] { write, read };

            // Lock the _i2C bus so multiple threads don't try to access it at the same time
            lock (_i2C)
            {
                // Execute the transation
                _i2C.Execute(readTransaction, I2CTimeout);
            }

            int sec = bcdToDec(returnedDateTime[0]) & 0x7f;
            int min = bcdToDec(returnedDateTime[1]);
            int hour = bcdToDec(returnedDateTime[2]) & 0x3f;
            bcdToDec(returnedDateTime[3]);
            int dayofmonth = bcdToDec(returnedDateTime[4]);
            int month = bcdToDec(returnedDateTime[5]);
            int year = bcdToDec(returnedDateTime[6]) + 2000;
            var dt = new DateTime(year, month, dayofmonth, hour, min, sec);
            return dt;
        }

        /// <summary>
        /// Set Real Time Clock to passed Date Time.
        /// </summary>
        /// <param name="datetime">DateTime containing date time to set RTC to.</param>

        public void SetDateTime(DateTime datetime)
        {
            var writeon1 = new byte[2] { 0x10, 0x80 };
            WriteRegister(writeon1);

            var writeon2 = new byte[2] { 0x0F, 0x84 };
            WriteRegister(writeon2);

            var sb = new byte[8]
                {
                    0x00, this.decToBcd(datetime.Second), this.decToBcd(datetime.Minute),
                    this.decToBcd(datetime.Hour), this.decToBcd((int) datetime.DayOfWeek),
                    this.decToBcd(datetime.Day), this.decToBcd(datetime.Month),
                    this.decToBcd(datetime.Year - 2000)
                };

            WriteRegister(sb);

            var writeon3 = new byte[2] { 0x12, 0x00 };
            WriteRegister(writeon3);
        }

        private void WriteRegister(Byte[] value)
        {
            I2CDevice.I2CWriteTransaction write = I2CDevice.CreateWriteTransaction(value);
            var writeTransaction = new I2CDevice.I2CTransaction[] { write };

            lock (_i2C)
            {
                _i2C.Execute(writeTransaction, I2CTimeout);
            }
        }

        #region Methods

        private byte bcdToDec(byte val)
        {
            return (byte)((val / 16 * 10) + (val % 16));
        }

        private byte decToBcd(int val)
        {
            return (byte)((val / 10 * 16) + (val % 10));
        }

        #endregion
    }
}